package cc.gpai.data_stru.bag;

import java.util.AbstractCollection;
import java.util.Collection;
import java.util.Iterator;
import java.util.Set;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicLong;

public abstract class AbstractBag<E> extends AbstractCollection<E> implements Bag<E> {

	@Override
	public boolean addAll(Collection<? extends E> c) {
		if (c instanceof Bag<?>) {
			if (c instanceof AbstractBag<?>) {
				@SuppressWarnings("unchecked")
				AbstractBag<E> that = (AbstractBag<E>) c;
				Set<Entry<E, AtomicLong>> set = that.entrySet();
				for (Entry<E, AtomicLong> entry : set) {
					E e = entry.getKey();
					modCount(e, entry.getValue().get());
				}
			} else {
				Bag<? extends E> that = (Bag<? extends E>) c;
				Set<? extends E> set = that.uniqueSet();
				for (E e : set) {
					modCount(e, that.getCount(e));
				}
			}
		} else
			for (E e : c) {
				add(e);
			}
		return true;
	}

	@SuppressWarnings("unchecked")
	@Override
	public long getCount(Object obj) {
		return modCount((E) obj, 0);
	}

	public abstract Set<Entry<E, AtomicLong>> entrySet();

	@Override
	public boolean isEmpty() {
		return uniqueSet().isEmpty();
	}

	@Override
	public boolean contains(Object o) {
		return uniqueSet().contains(o);
	}

	@Override
	public boolean containsAll(Collection<?> c) {
		for (Object obj : c) {
			if (!contains(obj)) {
				return false;
			}
		}
		return true;
	}

	public boolean removeAll(Collection<?> c) {
		boolean modified = false;
		if (c instanceof Bag<?>) {
			if (c instanceof AbstractBag<?>) {
				@SuppressWarnings("unchecked")
				AbstractBag<E> that = (AbstractBag<E>) c;
				Set<Entry<E, AtomicLong>> set = that.entrySet();
				for (Entry<E, AtomicLong> entry : set) {
					E e = entry.getKey();
					if (!modified) {
						modified |= contains(e);
					}
					modCount(e, -entry.getValue().get());
				}
			}else{
				@SuppressWarnings("unchecked")
                Bag<? extends E> that = (Bag<? extends E>) c;
				Set<? extends E> set = that.uniqueSet();
				for (E e : set) {
					if (!modified) {
						modified |= contains(e);
					}
					modCount(e, -that.getCount(e));
				}
			}
		} else
			for (Object obj : c) {
				modified |= remove(obj);
			}
		return modified;
	}

	@Override
	public boolean retainAll(Collection<?> c) {
		boolean modified = false;
		if (c instanceof Bag<?>) {
			Bag<?> that = (Bag<?>) c;
			for (E e : uniqueSet()) {
				setCount(e, Math.min(getCount(e), that.getCount(e)));
			}
		} else
			for (E e : uniqueSet()) {
				if (!c.contains(e))
					modified |= remove(e);
			}
		return modified;
	}

	@Override
	public int size() {
		int sum = 0;
		for (E e : uniqueSet()) {
			sum += getCount(e);
		}
		return sum;
	}

	@Override
	public Iterator<E> iterator() {
		return new BagIterator<E>(this);
	}

}

class BagIterator<E> implements Iterator<E> {
	private final Bag<E>      bag;
	private final Iterator<E> itr;
	private E                 e;
	private long              l;

	public BagIterator(Bag<E> bag) {
		super();
		this.bag = bag;
		itr = bag.uniqueSet().iterator();
		doNext();
	}

	private void doNext() {
		e = itr.next();
		l = bag.getCount(e);
	}

	private void step() {
		if (l < 1) {
			doNext();
		}
		l--;
	}

	@Override
	public boolean hasNext() {
		if (l > 0) {
			return true;
		}
		return itr.hasNext();
	}

	@Override
	public E next() {
		step();
		return e;
	}

	@Override
	public void remove() {
		bag.remove(e);
		step();
	}
}